最近整理了一下本科的毕业设计(2018年6月完成的),代码发布于https://github.com/metang326/malicious-URL-detection
URL异常检测本质上是一个分类问题,将输入的URL经过处理后得到特征,输入到分类其中,分类器输出分类结果,恶意的还是良性的。
在训练集和验证集的基础上训练了多个分类模型,训练集用于训练、验证集用来调整参数。
特征提取
数据预处理
首先,鉴于只有小部分URL的前缀有“http”,“https”以及“www”字段,并且这些字符对于恶意性检测并没有帮助,所以在预处理阶段我们将这些前缀删去。
但是要注意将前缀部分与其他部分出现的“http”等字段进行区分,如:“zylights.com/img/?us.battle.net/login/en/?ref=http%3A%2F%2Fkhcdcofus.battle.net%2Fd3%2Fen%2Findex&app=com-d3”中的“http”就出现在参数部分。
此外,由于URL的参数字段可能存在恶意代码,所以我们还需要对URL的编码问题进行考虑,以免影响检测的效果,比如参数字段中的“%20”代表的是空格。
URL结构划分
考虑到黑名单特征和主机特征的获取非常耗时,并且还会存在一定的噪声和缺失值,而基于内容的特征又非常“重量级”,并且在下载网页内容的过程中会给系统带来安全威胁。
因此,我们选择了基于词汇的URL特征,在能够取得较好检测效果的同时又可以保证“轻量级”和高安全性。为此,我们需要对URL进行深入的了解,URL的结构样例。
|
|
提取内容列表
由于协议部分对恶意URL的检测没有明显帮助,所以我们不考虑提取这部分作为特征。
在提取特征时,一条URL可以被分成域名、路径、查询参数、锚点四大部分,每个部分又分别是由数字、字母以及特殊符号构成的,我们可以根据特殊字符(例如“/”,“.”,“?”,“=”等)将URL切分为不同的token。这样,我们就可以将一条URL看作是由一组token组成的向量,从而获取到更加具体的特征。
针对URL整体以及它的每个部分,我们一共提取了85个特征,分为基于URL整体的特征、基于域名的特征、基于路径的特征、基于查询参数的特征以及基于锚点的特征。
其中的“恶意词出现次数”特征所统计的恶意词是根据malwaredomains.com下载的恶意域数据集(和训练集、验证集、测试集没有交集)统计出现次数较多的词。
Python自带的包可以根据恶意词的统计结果生成的词云图片,其中字体较大的数据出现频率更高。可以看出,“beget”“service”, “account”, “eby”, “paypal”, “login”, “confirm”这类词出现的频率很高,因为恶意网站经常模仿良性网站或者用一些有吸引力的词语,试图诱导用户输入账号以及登陆信息。
流行网站名是根据Alexa排名前500名的网站数据集进行统计得到的,有些恶意网站会通过模仿知名网站来迷惑用户。根据流行网站中出现的词,我们生成了词云后发现,流行词与恶意词存在着部分交集,比如“apple”、“google”、“facebook”这些词,因为恶意网站有时会假冒流行网站,选取相近的域名来迷惑用户。
所提取的全部特征见下表:
基于整体URL的特征 | 基于hostname的特征 | 基于path的特征 | 基于search的特征 | 基于hash的特征 |
---|---|---|---|---|
URL长度 | token个数 | token个数 | token个数 | token个数 |
字母比例 | 特殊字符个数 | path长度 | search长度 | |
数字比例 | 字母比例 | 目录深度(/) | 参数个数(&) | |
特殊符号的种类个数 | 数字比例 | 最长token | 方差 | |
特殊字符个数 | 出现点的次数(.) | 特殊符号的种类个数 | ||
URL深度(/) | 是否是IP地址 | 方差 | ||
出现点的次数(.) | 熵 | |||
存在@符号 | token方差 | |||
顶级域名TLD | hostname长度 | |||
出现恶意词的次数 | 字母出现次数 | |||
出现流行网站名的次数 | ||||
出现.php或者.exe的次数 | ||||
在除了开头位置出现http,www的次数 | ||||
字母出现次数 |
特征分析
为了分析我们所选取的特征是否可以对恶意URL进行有效地检验,我们比较了恶意URL和良性URL在不同特征下的平均值。
在随机划分情况下,不同的子集上相同特征的平均值几乎相同,仅有非常微小的区别。然而,当以标签作为划分依据之后,可以看出恶意URL集和良性URL集在相同特征下,平均值有明显的差异。
基于整体URL的特征 | 恶意URL平均值 | 良性URL平均值 |
---|---|---|
URL长度 | 55.78 | 47 |
字母比例 | 0.79 | 0.79 |
数字比例 | 0.075 | 0.06 |
特殊符号的种类个数 | 0.94 | 1.03 |
特殊字符个数 | 2.07 | 2.58 |
URL深度(/) | 2.78 | 2.32 |
出现点的次数(.) | 2.219 | 1.66 |
存在@符号 | 0.022 | 0 |
出现恶意词的次数 | 2.617 | 1.44 |
出现流行网站名的次数 | 0.922 | 0.67 |
出现.php或者.exe的次数 | 0.279 | 0.04 |
在除了开头位置出现http,www的次数 | 0.039 | 0 |
基于hostname的特征 | 恶意URL平均值 | 良性URL平均值 |
---|---|---|
token个数 | 2.48 | 2.35 |
特殊字符个数 | 0.15 | 0.06 |
字母比例 | 0.887 | 0.9 |
数字比例 | 0.022 | 0.006 |
出现点的次数(.) | 1.346 | 1.29 |
熵 | 3.398 | 3.31 |
token方差 | 3.943 | 3.2 |
hostname长度 | 15.8 | 14.36 |
基于path的特征 | 恶意URL平均值 | 良性URL平均值 |
---|---|---|
token个数 | 3.48 | 4.25 |
path长度 | 21.466 | 23.18 |
目录深度(/) | 1.739 | 1.31 |
最长token | 1.57 | 0.92 |
特殊符号的种类个数 | 0.795 | 0.97 |
方差 | 2.27 | 1.71 |
基于search的特征 | 恶意URL平均值 | 良性URL平均值 |
---|---|---|
token个数 | 1.46 | 0.56 |
search长度 | 11.04 | 2.75 |
参数个数(&) | 0.27 | 0.076 |
方差 | 1.05 | 0.37 |
模型训练
在提取特征的基础上,使用sklearn库中自带的分类模型在训练集上进行模型训练,根据各个分类器在验证集上的效果,在投票时设置了不同的权重。精确率高的分类器权重更高,也就是我们认为它的结果更加可信。
阈值在代码里的设置是,随机森林或者梯度提升树中只要有一个认为URL是恶意的,那就是恶意的,因为这两个模型的准确率比较高。剩下的四个分类器需要同时认为该URL是恶意的时候 才认为它是恶意的
实验结果
从kdnuggets上收集到了带标签(good/bad)的URL数据集,共416350条,其中异常数据(bad)71556条,占比17.19%;
正常数据(good)344794条,占比82.81%。
将全体数据划分为训练集(70%),验证集(15%)和测试集(15%),并且在每个集合中均保持异常数据所占比例相同。
分类器模型 | 准确度(%) | 精确度(%) | 召回率(%) |
---|---|---|---|
贝叶斯 | 85.88 | 60.82 | 50.25 |
AdaBoost | 92.84 | 86.05 | 69.65 |
随机森林 | 97.13 | 95.9 | 87.05 |
决策树 | 94.63 | 83.9 | 85.11 |
逻辑回归 | 90.86 | 83.29 | 58.58 |
梯度提升树 | 96.35 | 93.7 | 84.45 |
基于投票的分类器 | 97.1 | 92.51 | 90.48 |